import argparse
import glob
import json
import os
from pathlib import Path
from pickle import FALSE
from threading import Thread
import sys
sys.path.append('./')

import numpy as np
import torch
import yaml
from tqdm import tqdm
import cv2

from models.experimental import attempt_load
from fourpoint.dataset  import create_dataloader
from utils.general import coco80_to_coco91_class, check_dataset, check_file, check_img_size, box_iou, \
    non_max_suppression, scale_coords, xyxy2xywh, xywh2xyxy, set_logging, increment_path,polygon_iou
from fourpoint.fourpoint_loss import compute_loss_refinenet
from utils.metrics import ap_per_class, ap_per_class_fourpoint,ConfusionMatrix
from utils.plots import plot_images, output_to_target, plot_study_txt
from utils.torch_utils import select_device, time_synchronized
from fourpoint.fourpoint_loss import get_rec_box


def test(data,
         weights=None,
         batch_size=32,
         imgsz=640,
         conf_thres=0.001,
         iou_thres=0.6,  # for NMS
         save_json=False,
         single_cls=False,
         augment=False,
         verbose=False,
         model=None,
         dataloader=None,
         save_dir=Path(''),  # for saving images
         save_txt=False,  # for auto-labelling
         save_conf=False,
         plots=True,
         log_imgs=0,
         refine=False):  # number of logged images

    # Initialize/load model and set device
    training = model is not None
    if training:  # called by train.py
        device = next(model.parameters()).device  # get model device

    else:  # called directly
        set_logging()
        device = select_device(opt.device, batch_size=batch_size)
        save_txt = opt.save_txt  # save *.txt labels

        # Directories
        save_dir = Path(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))  # increment run
        (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True)  # make dir

        # Load model
        model = attempt_load(weights, map_location=device)  # load FP32 model
        imgsz = check_img_size(imgsz, s=model.stride.max())  # check img_size

        # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99
        # if device.type != 'cpu' and torch.cuda.device_count() > 1:
        #     model = nn.DataParallel(model)

    # Half
    half = device.type != 'cpu'  # half precision only supported on CUDA
    half = False
    if half:
        model.half()

    # Configure
    model.eval()
    is_coco = data.endswith('coco.yaml')  # is COCO dataset
    with open(data) as f:
        data = yaml.load(f, Loader=yaml.FullLoader)  # model dict
    check_dataset(data)  # check
    nc = 1 if single_cls else int(data['nc'])  # number of classes
    iouv = torch.linspace(0.5, 0.95, 10).to(device)  # iou vector for mAP@0.5:0.95
    niou = iouv.numel()

    # Logging
    log_imgs, wandb = min(log_imgs, 100), None  # ceil
    try:
        import wandb  # Weights & Biases
    except ImportError:
        log_imgs = 0

    # Dataloader
    if not training:
        img = torch.zeros((1, 3, imgsz, imgsz), device=device)  # init img
        _ = model(img.half() if half else img) if device.type != 'cpu' else None  # run once
        path = data['test'] if opt.task == 'test' else data['val']  # path to val/test images
        dataloader = create_dataloader(path, imgsz, batch_size, model.stride.max(), opt, pad=0.5, rect=True)[0]

    seen = 0
    confusion_matrix = ConfusionMatrix(nc=nc)
    names = {k: v for k, v in enumerate(model.names if hasattr(model, 'names') else model.module.names)}
    coco91class = coco80_to_coco91_class()
    s = ('%20s' + '%12s' * 6) % ('Class', 'Images', 'Targets', 'P', 'R', 'mAP@.5', 'mAP@.5:.95')
    p, r, f1, mp, mr, map50, map, t0, t1 = 0., 0., 0., 0., 0., 0., 0., 0., 0.
    loss = torch.zeros(3, device=device)
    jdict, stats, ap, ap_class, wandb_images = [], [], [], [], []
    for batch_i, (img, targets, paths, shapes) in enumerate(tqdm(dataloader, desc=s)):
        img = img.to(device, non_blocking=True)
        img = img.half() if half else img.float()  # uint8 to fp16/32
        img /= 255.0  # 0 - 255 to 0.0 - 1.0
        targets = targets.to(device)
        nb, _, height, width = img.shape  # batch size, channels, height, width

        with torch.no_grad():
            # Run model
            t = time_synchronized()
            (inf_out, _),feature = model(img, refine=True)  # inference and training outputs

            # Run NMS
            targets[:, 2:] *= torch.Tensor([width, height, width, height,width, height, width, height]).to(device)  # to pixels
            lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_txt else []  # for autolabelling
            t = time_synchronized()
            if refine:
                res,boxes = model.detector_(inf_out,feature)
                if len(boxes[0])==0:
                    stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls))
                    continue
                model.refine_net = model.refine_net.to(device)
                res = model.refine_net(res,boxes,train=False)
                t0 += time_synchronized() - t
                # print(res.shape)
                res_score = res[...,8]
                # print(res_score.shape)
                index_ = torch.max(res_score,1)[1]
                
                # print(torch.max(res_score,1)[1].shape)

                i=0
                correct = torch.zeros(res_score.shape[0], niou, dtype=torch.bool, device=device)
                for j in range(len(boxes)):
                    box = boxes[j]
                    labels = targets[targets[:, 0] == j, 1:]
                    nl = len(labels)
                    tcls = labels[:, 0].tolist() if nl else []
                    if box.shape[0]==0:
                        if nl:
                            stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls))
                    if box.shape[0]==1:
                        predi = res[i,index_[i]]
                        pred_loc = predi[:8]
                        target = targets[targets[:, 0] == j, 1:]
                        tar_loc = target[:,1:]
                        # img_ = (img[j]*255).permute(1,2,0).cpu().numpy().copy()
                        if True:
                            #### draw
                            x1 = int(pred_loc[0])
                            y1 = int(pred_loc[1])
                            x2 = int(pred_loc[2])
                            y2 = int(pred_loc[3])
                            x3 = int(pred_loc[4])
                            y3 = int(pred_loc[5])
                            x4 = int(pred_loc[6])
                            y4 = int(pred_loc[7])
                            img_1 = img_.copy()
                            img_1 = cv2.line(img_1,(x1,y1),(x2,y2),(255,255,0),2)
                            img_1 = cv2.line(img_1,(x3,y3),(x4,y4),(255,255,0),2)
                            cv2.imwrite('yanzheng/1.jpg',img_1)
                            tar_loc_ = tar_loc[0]
                            x1 = int(tar_loc_[0])
                            y1 = int(tar_loc_[1])
                            x2 = int(tar_loc_[2])
                            y2 = int(tar_loc_[3])
                            x3 = int(tar_loc_[4])
                            y3 = int(tar_loc_[5])
                            x4 = int(tar_loc_[6])
                            y4 = int(tar_loc_[7])
                            img_ = cv2.line(img_,(x1,y1),(x2,y2),(255,0,0),2)
                            img_ = cv2.line(img_,(x3,y3),(x4,y4),(255,0,0),2)
                            cv2.imwrite('yanzheng/2.jpg',img_)

                        iou = polygon_iou(pred_loc.tolist(),tar_loc.tolist())
                        iou = torch.tensor(iou).repeat(10).to(device)
                        correct[i] = iou>iouv
                        stats.append((correct.view(1,-1).cpu(), predi[8].view(1,1).cpu(), tcls, tcls))
                    if box.shape[0]>1:
                        re_ = []
                        for i in range(res.shape[0]):
                            predi = res[i,index_[i]]
                            pred_loc = predi[:8]
                            re_.append(pred_loc)
                        target = targets[targets[:, 0] == j, 1:]
                        tar_loc = target[:,1:]
                        print('paths',paths)

                    
                
            else:
            # Compute loss
            # if training:
            #     loss += compute_loss([x.float() for x in train_out], targets, model)[1][:3]  # box, obj, cls

            # Run NMS
                targets = get_rec_box(targets,6).to(device) 
                targets[:, 2:] *= torch.Tensor([width, height, width, height]).to(device)  # to pixels
                lb = [targets[targets[:, 0] == i, 1:] for i in range(nb)] if save_txt else []  # for autolabelling
                t = time_synchronized()
                output = non_max_suppression(inf_out, conf_thres=conf_thres, iou_thres=iou_thres, labels=lb)
                t1 += time_synchronized() - t

    

        # Statistics per image
                for si, pred in enumerate(output):
                    labels = targets[targets[:, 0] == si, 1:]
                    nl = len(labels)
                    tcls = labels[:, 0].tolist() if nl else []  # target class
                    path = Path(paths[si])
                    seen += 1

                    if len(pred) == 0:
                        if nl:
                            stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls))
                        continue

                    # Predictions
                    predn = pred.clone()
                    scale_coords(img[si].shape[1:], predn[:, :4], shapes[si][0], shapes[si][1])  # native-space pred

                    # Append to text file
                    if save_txt:
                        gn = torch.tensor(shapes[si][0])[[1, 0, 1, 0]]  # normalization gain whwh
                        for *xyxy, conf, cls in predn.tolist():
                            xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
                            line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
                            with open(save_dir / 'labels' / (path.stem + '.txt'), 'a') as f:
                                f.write(('%g ' * len(line)).rstrip() % line + '\n')

                    # W&B logging
                    if plots and len(wandb_images) < log_imgs:
                        box_data = [{"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]},
                                    "class_id": int(cls),
                                    "box_caption": "%s %.3f" % (names[cls], conf),
                                    "scores": {"class_score": conf},
                                    "domain": "pixel"} for *xyxy, conf, cls in pred.tolist()]
                        boxes = {"predictions": {"box_data": box_data, "class_labels": names}}  # inference-space
                        wandb_images.append(wandb.Image(img[si], boxes=boxes, caption=path.name))

                    # Append to pycocotools JSON dictionary
                    if save_json:
                        # [{"image_id": 42, "category_id": 18, "bbox": [258.15, 41.29, 348.26, 243.78], "score": 0.236}, ...
                        image_id = int(path.stem) if path.stem.isnumeric() else path.stem
                        box = xyxy2xywh(predn[:, :4])  # xywh
                        box[:, :2] -= box[:, 2:] / 2  # xy center to top-left corner
                        for p, b in zip(pred.tolist(), box.tolist()):
                            jdict.append({'image_id': image_id,
                                        'category_id': coco91class[int(p[5])] if is_coco else int(p[5]),
                                        'bbox': [round(x, 3) for x in b],
                                        'score': round(p[4], 5)})

                    # Assign all predictions as incorrect
                    correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool, device=device)
                    if nl:
                        detected = []  # target indices
                        tcls_tensor = labels[:, 0]

                        # target boxes
                        tbox = xywh2xyxy(labels[:, 1:5])
                        scale_coords(img[si].shape[1:], tbox, shapes[si][0], shapes[si][1])  # native-space labels
                        if plots:
                            confusion_matrix.process_batch(pred, torch.cat((labels[:, 0:1], tbox), 1))

                        # Per target class
                        for cls in torch.unique(tcls_tensor):
                            ti = (cls == tcls_tensor).nonzero(as_tuple=False).view(-1)  # prediction indices
                            pi = (cls == pred[:, 5]).nonzero(as_tuple=False).view(-1)  # target indices

                            # Search for detections
                            if pi.shape[0]:
                                # Prediction to target ious
                                ious, i = box_iou(predn[pi, :4], tbox[ti]).max(1)  # best ious, indices

                                # Append detections
                                detected_set = set()
                                for j in (ious > iouv[0]).nonzero(as_tuple=False):
                                    d = ti[i[j]]  # detected target
                                    if d.item() not in detected_set:
                                        detected_set.add(d.item())
                                        detected.append(d)
                                        correct[pi[j]] = ious[j] > iouv  # iou_thres is 1xn
                                        if len(detected) == nl:  # all targets already located in image
                                            break

                    # Append statistics (correct, conf, pcls, tcls)
                    stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))

                    # Plot images
                    if plots and batch_i < 3:
                        f = save_dir / f'test_batch{batch_i}_labels.jpg'  # labels
                        Thread(target=plot_images, args=(img, targets, paths, f, names), daemon=True).start()
                        f = save_dir / f'test_batch{batch_i}_pred.jpg'  # predictions
                        Thread(target=plot_images, args=(img, output_to_target(output), paths, f, names), daemon=True).start()

    # Compute statistics
    stats = [np.concatenate(x, 0) for x in zip(*stats)]  # to numpy
    if len(stats) and stats[0].any():
        p, r, ap, f1, ap_class = ap_per_class_fourpoint(*stats, plot=plots, save_dir=save_dir, names=names)
        p, r, ap50, ap = p[:, 0], r[:, 0], ap[:, 0], ap.mean(1)  # [P, R, AP@0.5, AP@0.5:0.95]
        mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
        nt = np.bincount(stats[3].astype(np.int64), minlength=nc)  # number of targets per class
    else:
        nt = torch.zeros(1)

    # Print results
    pf = '%20s' + '%12.3g' * 6  # print format
    print(pf % ('all', seen, nt.sum(), mp, mr, map50, map))

    # Print results per class
    if verbose and nc > 1 and len(stats):
        for i, c in enumerate(ap_class):
            print(pf % (names[c], seen, nt[c], p[i], r[i], ap50[i], ap[i]))

    # Print speeds
    # t = tuple(x / seen * 1E3 for x in (t0, t1, t0 + t1)) + (imgsz, imgsz, batch_size)  # tuple
    # if not training:
        # print('Speed: %.1f/%.1f/%.1f ms inference/NMS/total per %gx%g image at batch-size %g' % t)

    # Plots
    if plots:
        confusion_matrix.plot(save_dir=save_dir, names=list(names.values()))
        if wandb and wandb.run:
            wandb.log({"Images": wandb_images})
            wandb.log({"Validation": [wandb.Image(str(f), caption=f.name) for f in sorted(save_dir.glob('test*.jpg'))]})

    # Save JSON
    if save_json and len(jdict):
        w = Path(weights[0] if isinstance(weights, list) else weights).stem if weights is not None else ''  # weights
        anno_json = glob.glob('../coco/annotations/instances_val*.json')[0]  # annotations json
        pred_json = str(save_dir / f"{w}_predictions.json")  # predictions json
        print('\nEvaluating pycocotools mAP... saving %s...' % pred_json)
        with open(pred_json, 'w') as f:
            json.dump(jdict, f)

        try:  # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
            from pycocotools.coco import COCO
            from pycocotools.cocoeval import COCOeval

            anno = COCO(anno_json)  # init annotations api
            pred = anno.loadRes(pred_json)  # init predictions api
            eval = COCOeval(anno, pred, 'bbox')
            if is_coco:
                eval.params.imgIds = [int(Path(x).stem) for x in dataloader.dataset.img_files]  # image IDs to evaluate
            eval.evaluate()
            eval.accumulate()
            eval.summarize()
            map, map50 = eval.stats[:2]  # update results (mAP@0.5:0.95, mAP@0.5)
        except Exception as e:
            print('ERROR: pycocotools unable to run: %s' % e)

    # Return results
    if not training:
        s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
        print(f"Results saved to {save_dir}{s}")
    model.float()  # for training
    maps = np.zeros(nc) + map
    for i, c in enumerate(ap_class):
        maps[c] = ap[i]
    return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t


if __name__ == '__main__':
    parser = argparse.ArgumentParser(prog='test.py')
    parser.add_argument('--weights', nargs='+', type=str, default='last.pt', help='model.pt path(s)')
    parser.add_argument('--data', type=str, default='cfg/ccpd/ccpd_eval_data.yaml', help='*.data path')
    parser.add_argument('--batch-size', type=int, default=1, help='size of each image batch')
    parser.add_argument('--img-size', type=int, default=320, help='inference size (pixels)')
    parser.add_argument('--conf-thres', type=float, default=0.001, help='object confidence threshold')
    parser.add_argument('--iou-thres', type=float, default=0.6, help='IOU threshold for NMS')
    parser.add_argument('--task', default='val', help="'val', 'test', 'study'")
    parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    parser.add_argument('--single-cls', action='store_true', help='treat as single-class dataset')
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    parser.add_argument('--verbose', action='store_true', help='report mAP by class')
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--save-json', action='store_true', help='save a cocoapi-compatible JSON results file')
    parser.add_argument('--project', default='runs/test', help='save to project/name')
    parser.add_argument('--name', default='exp', help='save to project/name')
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    opt = parser.parse_args()
    opt.save_json |= opt.data.endswith('coco.yaml')
    opt.data = check_file(opt.data)  # check file
    print(opt)

    if opt.task in ['val', 'test']:  # run normally
        test(opt.data,
             opt.weights,
             opt.batch_size,
             opt.img_size,
             opt.conf_thres,
             opt.iou_thres,
             opt.save_json,
             opt.single_cls,
             opt.augment,
             opt.verbose,
             save_txt=opt.save_txt,
             save_conf=opt.save_conf,
             refine=True
             )

    elif opt.task == 'study':  # run over a range of settings and save/plot
        for weights in ['yolov3.pt', 'yolov3-spp.pt', 'yolov3-tiny.pt']:
            f = 'study_%s_%s.txt' % (Path(opt.data).stem, Path(weights).stem)  # filename to save to
            x = list(range(320, 800, 64))  # x axis
            y = []  # y axis
            for i in x:  # img-size
                print('\nRunning %s point %s...' % (f, i))
                r, _, t = test(opt.data, weights, opt.batch_size, i, opt.conf_thres, opt.iou_thres, opt.save_json,
                               plots=False)
                y.append(r + t)  # results and times
            np.savetxt(f, y, fmt='%10.4g')  # save
        os.system('zip -r study.zip study_*.txt')
        plot_study_txt(f, x)  # plot
